#include "Wt.h"

/* Update a Tcl array from a collection object */

int WtClnToArray(Tcl_Interp *interp, Tcl_Obj *cln,
                 Tcl_Obj *arrName, int multiVals)
{
  int ok = 1, i;
  WtCollection *clnRep = WtToCln(cln);
  Tcl_Obj *item, *key, *val, *slot, *list;
  Tcl_ObjType *listType = Tcl_GetObjType("list");

  for (i = 0; i < WtClnSize(cln); ++i) {
    item = WtGetClnItemAtIndex(cln, i);
    key = WtGetClnItemKey(cln, item);
    val = WtGetClnItemValue(cln, item);
    slot = Tcl_ObjGetVar2(interp, arrName, key, 0);
    if (multiVals) {
      if (!slot || slot->typePtr != listType) {
        list = Tcl_NewListObj(1, &val);
        if (!Tcl_ObjSetVar2(interp, arrName, key, list, TCL_LEAVE_ERR_MSG)) {
          ok = 0;
          break;
        }
      } else {
        if (Tcl_ListObjAppendElement(interp, slot, val) != TCL_OK) {
          ok = 0;
          break;
        }
      }
    } else {
      if (!Tcl_ObjSetVar2(interp, arrName, key, val, TCL_LEAVE_ERR_MSG)) {
        ok = 0;
        break;
      }
    }
  }

  return ok;
}

/* Get the collection being operated on */

Tcl_Obj *WtClnCmdGetCln(Tcl_Interp *interp, Tcl_Obj *obj, WtClnItemType *clnType)
{
  Tcl_Obj *found = WtGetPtr(obj, &WtClnType);

  if (found) {
    if (WtToCln(found)->itemType == clnType) {
      return found;
    } else {
      Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
        "Collection object has has the wrong type for this command.", NULL);
    }
  } else {
      Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
        "Parameter is not a valid collection object.", NULL);
  }

  return NULL;
}

/* Helper command processor to handle collection objects */

WtClnCmdStatus WtClnCmdHelper(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[], int *tclRet, WtClnItemType *clnType,
    int readOnly, int appendUsage)
{
  int ret = TCL_ERROR, status = WT_CLN_CMD_DONE, error = 0;
  int listLen;
  WtContext *w;
  const char *subCmd;
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  Tcl_Obj *cln, *list, *item;

  w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);

  if (objc < 2) {

    if (appendUsage) {
      WtClnCmdUsageError(interp, objv[0], readOnly);
    }

    return WT_CLN_CMD_MISSING;

  }

  subCmd = WtToString(objv[1]);

  if (!strcmp(subCmd, "exists")) {

    /* exists command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " collection key", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      item = WtFindClnItem(cln, objv[3]);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(item != NULL));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "get")) {

    /* get command */

    if (objc < 4 || objc > 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " collection key ?defaultValue?", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      item = WtFindClnItem(cln, objv[3]);
      if (item) {
        Tcl_SetObjResult(interp, item);
        ret = TCL_OK;
      } else if (objc > 4) {
        Tcl_SetObjResult(interp, objv[4]);
        ret = TCL_OK;
      } else {
        Tcl_AppendStringsToObj(interpRes, "No item with key \"",
          WtToString(objv[3]), "\" exists, and no default value was specified.",
          NULL);
      }
    }

  } else if (!strcmp(subCmd, "getAll")) {

    /* getAll command */

    if (objc < 4 || objc > 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " collection key ?defaultValue?", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      list = WtFindAllClnItems(cln, objv[3]);
      if (WtGetListSize(list)) {
        Tcl_SetObjResult(interp, list);
        ret = TCL_OK;
      } else if (objc > 4) {
        Tcl_SetObjResult(interp, objv[4]);
        ret = TCL_OK;
      } else {
        Tcl_AppendStringsToObj(interpRes, "No entry with key \"",
          WtToString(objv[3]), "\" exists, and no default value was specified.",
          NULL);
      }
    }

  } else if (!strcmp(subCmd, "add") && !readOnly) {

    /* add command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "incorrect usage: should be: \n",
        WtToString(objv[0]), " ", WtToString(objv[1]), " collection item",
        NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      WtAppendClnItem(cln, objv[3]);
      Tcl_SetObjResult(interp, objv[3]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "addAll") && !readOnly) {

    /* addAll command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " collection list",
        NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Value is not a list. Usage:\n",
          WtToString(objv[0]), " ", WtToString(objv[1]), " collection list",
          NULL);
      } else {
        WtAppendAllClnItems(cln, objv[3]);
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "items")) {

    /* items command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      list = WtGetClnItems(cln);
      Tcl_SetObjResult(interp, list);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "keys")) {

    /* keys command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      list = WtGetClnKeys(cln);
      Tcl_SetObjResult(interp, list);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "array")) {

    /* array command */

    if (objc < 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " collection arrayName", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      if (WtClnToArray(interp, cln, objv[3], 0)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "listArray")) {

    /* listArray command */

    if (objc < 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " collection arrayName", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      if (WtClnToArray(interp, cln, objv[3], 1)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "remove") && !readOnly) {

    /* remove command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " collection key", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      WtRemoveFromClnByKey(cln, objv[3], -1);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "removeList") && !readOnly) {

    /* removeList command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Argument is not a list. Usage:\n",
          WtToString(objv[0]), " ", WtToString(objv[1]), " collection list", NULL);
      } else {
        WtRemoveFromClnByKeyList(cln, objv[3], -1);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "clear") && !readOnly) {

    /* clear command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " collection", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      WtClearCollection(cln);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "copy")) {

    /* copy command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " collection", NULL);
    } else if (cln = WtClnCmdGetCln(interp, objv[2], clnType)) {
      Tcl_SetObjResult(interp, Tcl_DuplicateObj(cln));
      ret = TCL_OK;
    }

  } else {

    /* unknown command */

    status = WT_CLN_CMD_NOT_FOUND;

    if (appendUsage) {
      WtClnCmdUsageError(interp, objv[0], readOnly);
    }

  }

  *tclRet = ret;

  return status;
}

void WtClnCmdUsageError(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be one of:\n", NULL);
  WtClnCmdAppendUsage(interp, cmd, readOnly);
}

void WtClnCmdAppendUsage(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *res = Tcl_GetObjResult(interp);
  char *cmdStr = WtToString(cmd);

  if (readOnly) {
    Tcl_AppendStringsToObj(res,
      cmdStr, " exists collection key\n",
      cmdStr, " get collection key ?defaultValue?\n",
      cmdStr, " items collection\n",
      cmdStr, " keys collection\n",
      cmdStr, " array collection arrayName\n",
      cmdStr, " copy collection\n",
      cmdStr, " getAll collection key ?defaultValue?\n",
      cmdStr, " listArray collection arrayName\n",
      NULL);
  } else {
    Tcl_AppendStringsToObj(res,
      cmdStr, " exists collection key\n",
      cmdStr, " get collection key ?defaultValue?\n",
      cmdStr, " add collection item\n",
      cmdStr, " items collection\n",
      cmdStr, " keys collection\n",
      cmdStr, " array collection arrayName\n",
      cmdStr, " remove collection key\n",
      cmdStr, " removeList collection list\n",
      cmdStr, " copy collection\n",
      cmdStr, " clear collection\n",
      cmdStr, " getAll collection key ?defaultValue?\n",
      cmdStr, " addAll collection list\n",
      cmdStr, " listArray collection arrayName\n",
      NULL);
  }
}

/* Helper to manage collection objects. Implements the "object
   method args" command style. */

WtClnCmdStatus WtClnMethodHelper(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[], int *tclRet, Tcl_Obj *cln,
    int readOnly, int appendUsage)
{
  int ret = TCL_ERROR, status = WT_CLN_CMD_DONE, error = 0;
  int listLen;
  WtContext *w;
  const char *subCmd;
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  Tcl_Obj *list, *item;

  w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);

  if (objc < 2) {

    if (appendUsage) {
      WtClnMethodUsageError(interp, objv[0], readOnly);
    }

    return WT_CLN_CMD_MISSING;

  }

  subCmd = WtToString(objv[1]);

  if (!strcmp(subCmd, "exists")) {

    /* exists command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " name", NULL);
    } else {
      item = WtFindClnItem(cln, objv[2]);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(item != NULL));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "get")) {

    /* get command */

    if (objc < 3 || objc > 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ",
        WtToString(objv[1]), " name ", "?defaultValue?",
        NULL);
    } else {
      item = WtFindClnItem(cln, objv[2]);
      if (item) {
        Tcl_SetObjResult(interp, item);
        ret = TCL_OK;
      } else if (objc > 3) {
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      } else {
        Tcl_AppendStringsToObj(interpRes, "No item with key \"",
          WtToString(objv[2]), "\" exists, and no default value was specified.",
          NULL);
      }
    }

  } else if (!strcmp(subCmd, "getAll")) {

    /* getAll command */

    if (objc < 3 || objc > 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be:\n",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        "name ", "?defaultValue?", NULL);
    } else {
      list = WtFindAllClnItems(cln, objv[2]);
      if (WtGetListSize(list)) {
        Tcl_SetObjResult(interp, list);
        ret = TCL_OK;
      } else if (objc > 3) {
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      } else {
        Tcl_AppendStringsToObj(interpRes, "No entry with key \"",
          WtToString(objv[2]), "\" exists, and no default value was specified.",
          NULL);
      }
    }

  } else if (!strcmp(subCmd, "add") && !readOnly) {

    /* add command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "incorrect usage: should be: \n",
        WtToString(objv[0]), " ", WtToString(objv[1]), " item",
        NULL);
    } else {
      WtAppendClnItem(cln, objv[2]);
      Tcl_SetObjResult(interp, objv[2]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "addAll") && !readOnly) {

    /* addAll command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " list",
        NULL);
    } else {
      if (Tcl_ListObjLength(interp, objv[2], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Value is not a list. Usage:\n",
          WtToString(objv[0]), " ", WtToString(objv[1]), " list",
          NULL);
      } else {
        WtAppendAllClnItems(cln, objv[2]);
        Tcl_SetObjResult(interp, objv[2]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "items")) {

    /* items command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      list = WtGetClnItems(cln);
      Tcl_SetObjResult(interp, list);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "keys")) {

    /* keys command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      list = WtGetClnKeys(cln);
      Tcl_SetObjResult(interp, list);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "array")) {

    /* array command */

    if (objc < 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        "arrayName", NULL);
    } else {
      if (WtClnToArray(interp, cln, objv[2], 0)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "listArray")) {

    /* listArray command */

    if (objc < 3) {
      Tcl_AppendStringsToObj(interpRes, "incorrect usage: should be: \n",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        "arrayName", NULL);
    } else {
      if (WtClnToArray(interp, cln, objv[2], 1)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "remove") && !readOnly) {

    /* remove command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be:\n",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ", "name", NULL);
    } else {
      WtRemoveFromClnByKey(cln, objv[2], -1);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "removeList") && !readOnly) {

    /* removeList command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be:\n",
        WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
    } else {
      if (Tcl_ListObjLength(interp, objv[2], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Argument is not a list. Usage:\n",
          WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
      } else {
        WtRemoveFromClnByKeyList(cln, objv[2], -1);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "collection")) {

    /* collection command */

    Tcl_SetObjResult(interp, cln);
    ret = TCL_OK;

  } else if (!strcmp(subCmd, "copy")) {

    /* copy command */

    Tcl_SetObjResult(interp, Tcl_DuplicateObj(cln));
    ret = TCL_OK;

  } else if (!strcmp(subCmd, "clear") && !readOnly) {

    /* clear command */

    WtClearCollection(cln);
    ret = TCL_OK;

  } else {

    /* unknown command */

    status = WT_CLN_CMD_NOT_FOUND;

    if (appendUsage) {
      WtClnMethodUsageError(interp, objv[0], readOnly);
    }

  }

  *tclRet = ret;

  return status;
}

void WtClnMethodUsageError(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be one of:\n", NULL);
  WtClnMethodAppendUsage(interp, cmd, readOnly);
}

void WtClnMethodAppendUsage(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *res = Tcl_GetObjResult(interp);
  char *cmdStr = WtToString(cmd);

  if (readOnly) {
    Tcl_AppendStringsToObj(res,
      cmdStr, " exists key\n",
      cmdStr, " get key ?defaultValue?\n",
      cmdStr, " items\n",
      cmdStr, " keys\n",
      cmdStr, " collection\n",
      cmdStr, " array arrayName\n",
      cmdStr, " getAll key ?defaultValue?\n",
      cmdStr, " listArray arrayName\n",
      NULL);
  } else {
    Tcl_AppendStringsToObj(res,
      cmdStr, " exists key\n",
      cmdStr, " get key ?defaultValue?\n",
      cmdStr, " add item\n",
      cmdStr, " items\n",
      cmdStr, " keys\n",
      cmdStr, " remove key\n",
      cmdStr, " removeList list\n",
      cmdStr, " collection\n",
      cmdStr, " array arrayName\n",
      cmdStr, " clear\n",
      cmdStr, " getAll key ?defaultValue?\n",
      cmdStr, " addAll list\n",
      cmdStr, " listArray arrayName\n",
      NULL);
  }
}
